package edu.arizona.sista.learn.activity.model.args;

import java.util.Arrays;

import edu.arizona.sista.learn.activity.model.Bindings;


public class PredArg {

	protected String _variableName;
	protected String[] _cannotEqual = new String[0];
	protected String _boundValue;
	protected String _type;
	
//	public PredArg(){}
	
	protected PredArg(){
		_type = "arg";
	}
	
	public PredArg(String variableName, String value, String... cannotEqual){
		_variableName = variableName;
		_boundValue = value;
		_cannotEqual = cannotEqual;
	}
	
	public PredArg(String extendedVariableName, String value){
		_boundValue = value;
		if( extendedVariableName != null ){
			String[] varList = extendedVariableName.split("!");
			_variableName = varList[0];
	
			if( varList.length > 1 ){
				_cannotEqual = new String[varList.length-1];
				System.arraycopy(varList, 1, _cannotEqual, 0, _cannotEqual.length);
			}
		}
	}
	
	public boolean isBound() {
		return (_boundValue != null);
	}
	
	public Bindings getValidBinding(final PredArg testBinding, Bindings currentBindings){
		// If lhs already has a binding and lhs == rhs, returns current binding
		// If lhs already has a binding and lhs != rhs, returns current binding
		if(isBound())
			return _boundValue.equals(testBinding._boundValue) ? currentBindings : null;
		
//		if( _variableName == null && _boundValue == null )
//			System.out.println("Cannot Equal: "+Arrays.toString(_cannotEqual));
		
		// Valid lhs (i.e. has no binding)
		if( currentBindings == null)
			currentBindings = new Bindings();
			
		String binding = currentBindings.get(this);
//		System.out.println("\nHEYO   "+binding);
		
		if( binding == null ) {
			// Valid binding
			
			// But first we have to make sure we aren't binding to the same
			//   arg as another variable if it's disallowed
//			String[] mustNotBeEquals = _variableName.split("!");
			for( int i=0; i<_cannotEqual.length; i++ ) {//start at 1 because 0 should be this arg's name
				//TODO: change Bindings to key on a String?
				if(  testBinding._boundValue == null ){
					if( _cannotEqual == null)
					throw new RuntimeException("Cannot bind against an unbound PredArg: "+testBinding);
//				System.out.println(testBinding._boundValue+" "+currentBindings.get(new PredArg(_cannotEqual[i], null))+" "+testBinding._boundValue.equals(currentBindings.get(new PredArg(_cannotEqual[i], null))));
				} else
				if(  testBinding._boundValue.equals(currentBindings.get(new PredArg(_cannotEqual[i], null)) )  )
					return null;
				else
				if(  testBinding._boundValue == _cannotEqual[i] )
					return null;
			}
			
			// We want to put the most specific between this.type and testBinding.type
			//  testBinding would always be >= specific, so we'll copy it (for its type)
			//  Could use reflection for this but it's a mess
			PredArg copy = testBinding.copy();
			copy.setVariableName(this.getVariableName());
			copy._boundValue = this._boundValue;
			copy._cannotEqual = this._cannotEqual;
			if( testBinding._boundValue != null)
				currentBindings.put(copy, testBinding._boundValue);
			else
				//FIXME: currently only binds first cannot equal here
				currentBindings.put(copy, "!"+testBinding._cannotEqual[0]);
			return currentBindings;
		} else
		if( binding.equals(testBinding._boundValue) ) {
			return currentBindings;
		} else
		if( binding.contains("!") ){
			String[] cannotEquals = binding.split("!");
			for( String cannotEqual : cannotEquals )
				if( cannotEqual.equals(testBinding._boundValue) )
					return null;
			
			// We want to put the most specific between this.type and testBinding.type
			//  testBinding would always be >= specific, so we'll copy it (for its type)
			//  Could use reflection for this but it's a mess
			PredArg copy = testBinding.copy();
			copy.setVariableName(this.getVariableName());
			copy._boundValue = this._boundValue;
			copy._cannotEqual = this._cannotEqual;
			currentBindings.remove(copy);
			currentBindings.put(copy, testBinding._boundValue);
			
			return currentBindings; 
		}

		return null;
	}
	
	public boolean lessSpecificThan(PredArg otherArg){
		if( !( otherArg.getClass().isInstance(this) ) )
			return true;
//		else
//		//Hack to see if this doesn't have as many disclusions
//		// as the other
//		if( this.getCannotEqual().length <
//				otherArg.getCannotEqual().length){
//			return true;
//		}
		
		return false;
	}
	
	public boolean bindableTo(PredArg testBinding, Bindings currentBindings){
		return this.getValidBinding(testBinding, currentBindings) == null ? false : true;
	}
	
	public void bind(String value) {
		if (isBound())
			throw new RuntimeException("Object already bound!");
		_boundValue = value;
		_variableName = null;
	}
	
	public PredArg copy(){
		return new PredArg(_variableName, _boundValue, Arrays.copyOf(_cannotEqual,_cannotEqual.length) );
	}
	
	public String toString(){
		String printName = "";
		if( _boundValue != null )
			printName = _boundValue;
		else {
			printName+= _variableName != null ? _variableName : "";
			if( getCannotEqual() != null )
				for( String cannotEqual : getCannotEqual() )
					printName+= "!"+cannotEqual;
		}
			
		return _type+":"+ printName;
	}
	
	public void setVariableName(String variableName) {
		_variableName = variableName;
	}
	
	public String getVariableName() {
		return _variableName;
	}
	
	public String[] getCannotEqual(){
		return _cannotEqual;
	}
	
	public String getBoundValue() {
		return _boundValue;
	}
	
	@Override
	public int hashCode(){
//		return _variableName.split("!")[0].hashCode();
		
		return _variableName==null ? 0 : _variableName.hashCode();
	}
	
	@Override
	public boolean equals(Object object){
		return getVariableName() != null && 
			   object != null &&
			   object instanceof PredArg &&
			   hashCode() == object.hashCode();
	}
	
}
